//The MIT License

//Copyright (c) 2009 Lluis Sanchez, Nick Darnell

//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:

//The above copyright notice and this permission notice shall be included in
//all copies or substantial portions of the Software.

//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//THE SOFTWARE.

using System;
using System.Reflection;
using System.Reflection.Emit;
using Polymorph.Properties;

namespace Polymorph
{
    /// <summary>
    /// The <see cref="CodeCast"/> class represents an explicit cast expression.  Even
    /// if the result type of the expression supports an implicit cast to the target type,
    /// a <see cref="CodeCast"/> will need to be used to find the implicit cast.
    /// </summary>
    public class CodeCast : CodeExpression
    {
        private Type m_type;
        private CodeExpression m_exp;

        /// <summary>
        /// Initializes a new instance of <see cref="CodeCast"/>.
        /// </summary>
        /// <param name="type">The type to cast the expression to.</param>
        /// <param name="exp">The expression to cast.</param>
        public CodeCast(Type type, CodeExpression exp)
        {
            m_type = type;
            m_exp = exp;
        }

        /// <summary>
        /// Generates the IL for a cast expression.
        /// </summary>
        /// <param name="gen">The IL generator to use.</param>
        public override void Generate(ILGenerator gen)
        {
            m_exp.Generate(gen);

            Type typeObj = m_exp.ResultType;

            if (m_type.IsAssignableFrom(typeObj))
            {
                if (typeObj.IsValueType)
                    gen.Emit(OpCodes.Box, typeObj);
                return;
            }
            else if (m_type.IsValueType && typeObj == typeof(object))
            {
                // Unbox
                gen.Emit(OpCodes.Unbox, m_type);
                CodeGenerationHelper.LoadFromValue(gen, m_type);
                return;
            }
            else if (typeObj.IsAssignableFrom(m_type))
            {
                // Sub s = (Sub)base
                gen.Emit(OpCodes.Castclass, m_type);
                return;
            }
            else if (CodeGenerationHelper.IsNumber(m_type) && CodeGenerationHelper.IsNumber(typeObj))
            {
                switch (Type.GetTypeCode(m_type))
                {
                    case TypeCode.Byte:
                        gen.Emit(OpCodes.Conv_U1);
                        return;
                    case TypeCode.Double:
                        gen.Emit(OpCodes.Conv_R8);
                        return;
                    case TypeCode.Int16:
                        gen.Emit(OpCodes.Conv_I2);
                        return;
                    case TypeCode.Int32:
                        gen.Emit(OpCodes.Conv_I4);
                        return;
                    case TypeCode.Int64:
                        gen.Emit(OpCodes.Conv_I8);
                        return;
                    case TypeCode.SByte:
                        gen.Emit(OpCodes.Conv_I1);
                        return;
                    case TypeCode.Single:
                        gen.Emit(OpCodes.Conv_R4);
                        return;
                    case TypeCode.UInt16:
                        gen.Emit(OpCodes.Conv_U2);
                        return;
                    case TypeCode.UInt32:
                        gen.Emit(OpCodes.Conv_U4);
                        return;
                    case TypeCode.UInt64:
                        gen.Emit(OpCodes.Conv_U8);
                        return;
                }
            }

            MethodInfo imp = m_type.GetMethod("op_Implicit", new Type[] { typeObj });
            if (imp != null)
            {
                gen.Emit(OpCodes.Call, imp);
                return;
            }

            foreach (MethodInfo m in typeObj.GetMember("op_Explicit"))
            {
                if (m.ReturnType == m_type)
                {
                    gen.Emit(OpCodes.Call, m);
                    return;
                }
            }

            throw new InvalidOperationException(string.Format(Resources.CanNotCast0To1, 
                typeObj, m_type));
        }

        /// <summary>
        /// Writes a human readable version of the cast expression.
        /// </summary>
        /// <param name="cw">The code writer to use.</param>
        public override void PrintCode(CodeWriter cw)
        {
            Type typeObj = m_exp.ResultType;
            if (m_type.IsAssignableFrom(typeObj))
                return;

            cw.Write("((" + m_type.FullName + ")");
            m_exp.PrintCode(cw);
            cw.Write(")");
        }

        /// <summary>
        /// The result type of the cast.
        /// </summary>
        public override Type ResultType
        {
            get { return m_type; }
        }
    }
}